/*
 *   Copyright (c) 2015, Andrew Romanenko <melanhit@gmail.com>
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice, this
 *      list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of the project nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
 *   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 *   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <stdint.h>

#include "../akmos.h"
#include "../macro.h"
#include "../cipher.h"

#include "threefish.h"
#include "threefish_mix.h"

akmos_cipher_algo_t akmos_algo_threefish_256 = {
    AKMOS_ALGO_THREEFISH_256, "Threefish-256",
    32, 32, 32, 32,
    (void *)akmos_threefish_256_setkey,
    (void *)akmos_threefish_256_encrypt,
    (void *)akmos_threefish_256_decrypt
};

akmos_cipher_algo_t akmos_algo_threefish_512 = {
    AKMOS_ALGO_THREEFISH_512, "Threefish-512",
    64, 64, 64, 64,
    (void *)akmos_threefish_512_setkey,
    (void *)akmos_threefish_512_encrypt,
    (void *)akmos_threefish_512_decrypt
};

akmos_cipher_algo_t akmos_algo_threefish_1024 = {
    AKMOS_ALGO_THREEFISH_1024, "Threefish-1024",
    128, 128, 128, 128,
    (void *)akmos_threefish_1024_setkey,
    (void *)akmos_threefish_1024_encrypt,
    (void *)akmos_threefish_1024_decrypt
};

#define WORDS_256   4
#define ROUNDS_256  72
#define SKEYS_256   ((ROUNDS_256 / 4) + 1)

#define WORDS_512   8
#define ROUNDS_512  72
#define SKEYS_512   ((ROUNDS_512 / 4) + 1)

#define WORDS_1024  16
#define ROUNDS_1024 80
#define SKEYS_1024  ((ROUNDS_1024 / 4) + 1)

#define CONST_240   0x1bd11bdaa9fc1a22

#define SZ_U64      sizeof(uint64_t)

void akmos_threefish_256_setkey(akmos_threefish_256_t *ctx,
                                const uint8_t *key,
                                size_t len)
{
    uint64_t k[WORDS_256 + 1], *S;
    int i, y;

    for(i = 0; i < WORDS_256; i++)
        k[i] = PACK64R(key + (i * SZ_U64));

    k[WORDS_256] = CONST_240;

    for(i = 0; i < WORDS_256; i++)
        k[WORDS_256] ^= k[i];

    for(i = 0; i < SKEYS_256; i++) {
        S = ctx->S + (WORDS_256*i);

        for(y = 0; y < WORDS_256; y++)
            S[y] = k[(i+y)%(WORDS_256+1)];

        S[WORDS_256-1] += i;
    }
}

void akmos_threefish_256_encrypt(akmos_threefish_256_t *ctx,
                                 const uint8_t *in_blk,
                                 uint8_t *out_blk)
{
    uint64_t s[WORDS_256], *S;
    int i, y;

    for(i = 0; i < WORDS_256; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    for(i = 0, S = ctx->S; i < 9; i++, S += WORDS_256) {
        for(y = 0; y < WORDS_256; y++)
            s[y] += S[y];

        threefish_256_emix(s, 14, 16, 52, 57);
        threefish_256_emix(s, 23, 40,  5, 37);

        for(y = 0, S += WORDS_256; y < WORDS_256; y++)
            s[y] += S[y];

        threefish_256_emix(s, 25, 33, 46, 12);
        threefish_256_emix(s, 58, 22, 32, 32);
    }

    for(i = 0; i < WORDS_256; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i] + S[i]);
}

void akmos_threefish_256_decrypt(akmos_threefish_256_t *ctx,
                                 const uint8_t *in_blk,
                                 uint8_t *out_blk)
{
    uint64_t s[WORDS_256], *S;
    int i, y;

    for(i = 0; i < WORDS_256; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    S = ctx->S + (WORDS_256 * (SKEYS_256 - 1));
    for(i = 0; i < WORDS_256; i++)
        s[i] -= S[i];

    for(i = ROUNDS_256 / 8; i > 0; i--) {
        threefish_256_dmix(s, 32, 32, 58, 22);
        threefish_256_dmix(s, 46, 12, 25, 33);

        for(y = 0, S -= WORDS_256; y < WORDS_256; y++)
            s[y] -= S[y];

        threefish_256_dmix(s,  5, 37, 23, 40);
        threefish_256_dmix(s, 52, 57, 14, 16);

        for(y = 0, S -= WORDS_256; y < WORDS_256; y++)
            s[y] -= S[y];
    }

    for(i = 0; i < WORDS_256; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i]);
}

void akmos_threefish_512_setkey(akmos_threefish_512_t *ctx,
                                const uint8_t *key,
                                size_t len)
{
    uint64_t k[WORDS_512 + 1], *S;
    int i, y;

    for(i = 0; i < WORDS_512; i++)
        k[i] = PACK64R(key + (i * SZ_U64));

    k[WORDS_512] = CONST_240;

    for(i = 0; i < WORDS_512; i++)
         k[WORDS_512] ^= k[i];

    for(i = 0; i < SKEYS_512; i++) {
        S = ctx->S + (WORDS_512 * i);

        for(y = 0; y < WORDS_512; y++)
            S[y] = k[(i+y)%(WORDS_512+1)];

        S[WORDS_512-1] += i;
    }
}

void akmos_threefish_512_encrypt(akmos_threefish_512_t *ctx,
                                 const uint8_t *in_blk,
                                 uint8_t *out_blk)
{
    uint64_t s[WORDS_512], *S;
    int i, y;

    for(i = 0; i < WORDS_512; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    for(i = 0, S = ctx->S; i < ROUNDS_512 / 8; i++, S += WORDS_512) {
        for(y = 0; y < WORDS_512; y++)
            s[y] += S[y];

        threefish_512_emix1(s, 46, 36, 19, 37);
        threefish_512_emix2(s, 33, 27, 14, 42);
        threefish_512_emix3(s, 17, 49, 36, 39);
        threefish_512_emix4(s, 44,  9, 54, 56);

        for(y = 0, S += WORDS_512; y < WORDS_512; y++)
            s[y] += S[y];
                                                                            
        threefish_512_emix1(s, 39, 30, 34, 24);
        threefish_512_emix2(s, 13, 50, 10, 17);
        threefish_512_emix3(s, 25, 29, 39, 43);
        threefish_512_emix4(s,  8, 35, 56, 22);
    }

    for(i = 0; i < WORDS_512; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i] + S[i]);
}

void akmos_threefish_512_decrypt(akmos_threefish_512_t *ctx,
                                 const uint8_t *in_blk,
                                 uint8_t *out_blk)
{
    uint64_t s[WORDS_512], *S;
    int i, y;

    for(i = 0; i < WORDS_512; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    S = ctx->S + (WORDS_512 * (SKEYS_512 - 1));
    for(i = 0; i < WORDS_512; i++)
        s[i] -= S[i];

    for(i = ROUNDS_512 / 8; i > 0; i--) {
        threefish_512_dmix1(s, 22, 56, 35,  8);
        threefish_512_dmix2(s, 43, 39, 29, 25);
        threefish_512_dmix3(s, 17, 10, 50, 13);
        threefish_512_dmix4(s, 24, 34, 30, 39);

        for(y = 0, S -= WORDS_512; y < WORDS_512; y++)
            s[y] -= S[y];

        threefish_512_dmix1(s, 56, 54,  9, 44);
        threefish_512_dmix2(s, 39, 36, 49, 17);
        threefish_512_dmix3(s, 42, 14, 27, 33);
        threefish_512_dmix4(s, 37, 19, 36, 46);

        for(y = 0, S -= WORDS_512; y < WORDS_512; y++)
            s[y] -= S[y];
    }

    for(i = 0; i < WORDS_512; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i]);
}

void akmos_threefish_1024_setkey(akmos_threefish_1024_t *ctx,
                                 const uint8_t *key,
                                 size_t len)
{
    uint64_t k[WORDS_1024 + 1], *S;
    int i, y;

    for(i = 0; i < WORDS_1024; i++)
        k[i] = PACK64R(key + (i * SZ_U64));

    k[WORDS_1024] = CONST_240;
    for(i = 0; i < WORDS_1024; i++)
        k[WORDS_1024] ^= k[i];

    for(i = 0; i < SKEYS_1024; i++) {
        S = ctx->S + (WORDS_1024 * i);

        for(y = 0; y < WORDS_1024; y++)
            S[y] = k[(i+y)%(WORDS_1024+1)];

        S[WORDS_1024-1] += i;
    }
}

void akmos_threefish_1024_encrypt(akmos_threefish_1024_t *ctx,
                                  const uint8_t *in_blk,
                                  uint8_t *out_blk)
{
    uint64_t s[WORDS_1024], *S;
    int i, y;

    for(i = 0; i < WORDS_1024; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    for(i = 0, S = ctx->S; i < ROUNDS_1024 / 8; i++, S += WORDS_1024) {
        for(y = 0; y < WORDS_1024; y++)
            s[y] += S[y];

        threefish_1024_emix1(s, 24, 13,  8, 47,  8, 17, 22, 37);
        threefish_1024_emix2(s, 38, 19, 10, 55, 49, 18, 23, 52);
        threefish_1024_emix3(s, 33,  4, 51, 13, 34, 41, 59, 17);
        threefish_1024_emix4(s,  5, 20, 48, 41, 47, 28, 16, 25);

        for(y = 0, S += WORDS_1024; y < WORDS_1024; y++)
            s[y] += S[y];

        threefish_1024_emix1(s, 41,  9, 37, 31, 12, 47, 44, 30);
        threefish_1024_emix2(s, 16, 34, 56, 51,  4, 53, 42, 41);
        threefish_1024_emix3(s, 31, 44, 47, 46, 19, 42, 44, 25);
        threefish_1024_emix4(s,  9, 48, 35, 52, 23, 31, 37, 20);
    }

    for(i = 0; i < WORDS_1024; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i] + S[i]);
}

void akmos_threefish_1024_decrypt(akmos_threefish_1024_t *ctx,
                                  const uint8_t *in_blk,
                                  uint8_t *out_blk)
{
    uint64_t s[WORDS_1024], *S;
    int i, y;

    for(i = 0; i < WORDS_1024; i++)
        s[i] = PACK64R(in_blk + (i * SZ_U64));

    S = ctx->S + (WORDS_1024 * (SKEYS_1024 - 1));
    for(i = 0; i < WORDS_1024; i++)
        s[i] -= S[i];

    for(i = ROUNDS_1024 / 8; i > 0; i--) {
        threefish_1024_dmix1(s, 20, 37, 31, 23, 52, 35, 48,  9);
        threefish_1024_dmix2(s, 25, 44, 42, 19, 46, 47, 44, 31);
        threefish_1024_dmix3(s, 41, 42, 53,  4, 51, 56, 34, 16);
        threefish_1024_dmix4(s, 30, 44, 47, 12, 31, 37,  9, 41);

        for(y = 0, S -= WORDS_1024; y < WORDS_1024; y++)
            s[y] -= S[y];

        threefish_1024_dmix1(s, 25, 16, 28, 47, 41, 48, 20,  5);
        threefish_1024_dmix2(s, 17, 59, 41, 34, 13, 51,  4, 33);
        threefish_1024_dmix3(s, 52, 23, 18, 49, 55, 10, 19, 38);
        threefish_1024_dmix4(s, 37, 22, 17,  8, 47,  8, 13, 24);

        for(y = 0, S -= WORDS_1024; y < WORDS_1024; y++)
            s[y] -= S[y];
    }
    
    for(i = 0; i < WORDS_1024; i++)
        UNPACK64R(out_blk + (i * SZ_U64), s[i]);
}
